const axios = require("axios").default.create({
  headers: {
    "User-Agent": "Mozilla/5.0 (X11; CrOS x86_64 10032.86.0) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/63.0.3239.140 Safari/537.36",
    'referer': 'https://jianliao.com/',
    'x-language': 'zh',
    'x-requested-with': 'XMLHttpRequest'
  },
  validateStatus: s => s == 200,
  maxRedirects: 0,
  timeout: 30 * 1000
});
const qs = require("querystring");
const url = require("url");
const fs = require("fs");
const path = require("path");
const Cookie = require("cookie");
const util = require("util");
const buffer = require("buffer");
const FormData = require("form-data");
const jsonBeautify = require("json-beautify");
const SetCookieParser = require("set-cookie-parser");
const Toolbox = require("./toolbox");
const CommonAxerrHandlerGen = require("./CommonAxerrHandler");

class Jianliao {
  constructor() {
    this.__cookies = [{
      name: "Hm_lpvt_32f5a4ae87ac4925dc736fc8dd1a9a1a",
      value: `${parseInt(Date.now() / 1000)}`
    }];
    this.account_infomation = {
      accountId: "", //"5befcf8f7c8c81787e7bbacb"
      accountToken: "", //"eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJsb2dpbiI6ImVtYWlsIiwiX2lkIjoiNWJlZmNmOGY3YzhjODE3ODdlN2JiYWNiIn0.aZMBLNDRlwiS5Y77nNATgfr8crtRKHuKohKvph0vMko"
      createAt: "", //"2018-11-17T08:21:35.279Z"
      emailAddress: "", //..
      updateAt: "", //"2018-11-17T08:21:35.279Z"
    };
    this.cache = {
      me: null, //暂存getMe的结果
    }
  }

  /**
   *
   * @api private
   * @param {String} set_cookie_header_value
   * @memberof Jianliao
   */
  __dealWithSetCookie(set_cookie_header_value) {
    if (util.isArray(set_cookie_header_value)) {
      return set_cookie_header_value.forEach(e => this.__dealWithSetCookie(e));
    }
    let parsed = SetCookieParser(set_cookie_header_value);
    parsed.forEach(psd => {
      let i = this.__cookies.findIndex(c => c.name === psd.name);
      if (i >= 0) {
        this.__cookies[i].value = psd.value;
      } else {
        this.__cookies.unshift({
          name: psd.name,
          value: psd.value
        })
      }
    })
  }

  get cookies_as_request_header() {
    return this.__cookies.filter(e => e.name && e.value).map(c => Cookie.serialize(c.name, c.value, {
      encode: v => v
    })).join("; ")
  }

  /**
   * 
   * @param {String} jianliao_url 
   */
  __visit(jianliao_url) {
    return new Promise(resolve => {
      axios.get(jianliao_url, {
        headers: {
          cookie: this.cookies_as_request_header
        },
        validateStatus: s => true
      }).then(axresp => {
        this.__dealWithSetCookie(axresp.headers['set-cookie']);
        resolve();
      }).catch(axerr => {
        debugger
        console.log(axerr);
        resolve()
      })
    })
  }

  /**
   *
   * @description 保留所有HTTP返回信息,后面全都要用
   * @returns {Promise<{ok:Boolean,msg:String,data:{accountId:string,_id:String,name:String}}>}
   * @memberof Jianliao
   */
  __getMe() {
    return new Promise(resolve => {
      axios.get(`https://jianliao.com/v2/users/me`, {
        headers: {
          cookie: this.cookies_as_request_header
        }
      }).then(axresp => {
        // debugger
        if (axresp.data && axresp.data._id && axresp.data.name && axresp.data.id && axresp.data.accountId) {
          resolve({
            ok: true,
            msg: "ok",
            data: axresp.data
          })
        } else {
          throw new Error(`axresp.data is toxic:${util.inspect(axresp.data)}`)
        }
      }).catch(axerr => {
        CommonAxerrHandlerGen(resolve)(axerr);
      })
    })
  }

  /**
   * @returns {Promise<{ok:Boolean,msg:String,data:{_id:string}}>}
   * @param {String} teamId 
   * @param {String} roomId 
   * @param {String} body 
   * @param {Array<{category:"file",data:{fileSize:Number,fileKey:String,fileName:String,fileType:String} }>} attachments 
   */
  sendMessageMarkdown(body, teamId, roomId, attachments = []) {
    return new Promise(async resolve => {
      if (!this.cache.me) {
        let o_me = await this.__getMe();
        if (!o_me.ok) {
          return resolve({
            ok: false,
            msg: `error when __getMe():${o_me.msg}`
          })
        }
        this.cache.me = o_me.data;
      }
      /**@type {{_id:String}} */
      let me = this.cache.me;

      axios.post(`https://jianliao.com/v2/messages`, {
        attachments: attachments,
        body: body,
        creator: me,
        _creatorId: me._id,
        _roomId: roomId,
        _teamId: teamId,
        displayType: "markdown" //其实还有个text选项
      }, {
          headers: {
            cookie: this.cookies_as_request_header
          }
        }).then(axresp => {
          if (axresp.data && axresp.data._id && axresp.data._targetId && axresp.data.displayType) {
            resolve({
              ok: true,
              msg: "ok",
              data: {
                _id: axresp.data._id
              }
            })
          } else {
            throw new Error(`axresp.data is toxic:${util.inspect(axresp.data)}`)
          }
        }).catch(axerr => {
          CommonAxerrHandlerGen(resolve)(axerr)
        })
    })
  }

  /**
   * @returns {Promise<{ok:Boolean,msg:String,data:{_id:string}}>}
   * @param {String} teamId 
   * @param {String} roomId 
   * @param {string} title
   * @param {String} html_body 
   * @param {Array<{category:"file",data:{fileSize:Number,fileKey:String,fileName:String,fileType:String} }>} attachments 
   */
  sendMesageRichtext(html_body, title, teamId, roomId, attachments) {
    return new Promise(async resolve => {
      if (!this.cache.me) {
        let o_me = await this.__getMe();
        if (!o_me.ok) {
          return resolve({
            ok: false,
            msg: `error when __getMe():${o_me.msg}`
          })
        }
        this.cache.me = o_me.data;
      }
      /**@type {{_id:String}} */
      let me = this.cache.me;

      axios.post(`https://jianliao.com/v2/messages`, {
        attachments: [{
          category: "rtf",
          data: {
            text: html_body,
            title: title
          }
        }].concat(attachments),
        body: title,
        creator: me,
        _creatorId: me._id,
        _roomId: roomId,
        _teamId: teamId,
      }, {
          headers: {
            cookie: this.cookies_as_request_header
          }
        }).then(axresp => {
          if (axresp.data && axresp.data._id && axresp.data._targetId && axresp.data.displayType) {
            resolve({
              ok: true,
              msg: "ok",
              data: {
                _id: axresp.data._id
              }
            })
          } else {
            throw new Error(`axresp.data is toxic:${util.inspect(axresp.data)}`)
          }
        }).catch(axerr => {
          CommonAxerrHandlerGen(resolve)(axerr)
        })
    })
  }

  /**
   * @returns {Promise<{ok:Boolean,msg:String,data:{message_id:string}}>}
   * @param {String} teamId 
   * @param {String} roomId 
   * @param {String} full_path 
   * @param {any} extra_info
   * @param {Number} size_limit 
   */
  sendMessageToSaveFile(teamId, roomId, full_path, size_limit = 999 * 1024 * 1024, extra_info = {}) {
    return new Promise(async resolve => {
      let o_strike = await this.getStrikeToken();
      if (!o_strike.ok) {
        return resolve({
          ok: false,
          msg: `error get strike token:${o_strike.msg}`
        })
      }
      let o_up = await JianliaoWrapper.uploadFile(full_path,
        o_strike.token, size_limit);
      if (!o_up.ok) {
        return resolve({
          ok: false,
          msg: `error uploading :${o_up.msg}`
        })
      }
      let o_sendMessage = await this.sendMessageMarkdown(
        `\`\`\`json\n${jsonBeautify(
          {
            path: path.basename(full_path),
            extra_info: extra_info
          }
          , null, 2, 20)} \n\`\`\``, teamId, roomId,
        o_up.parts.map(e => {
          return {
            category: "file",
            data: {
              fileSize: e.data.fileSize,
              fileName: e.data.fileName,
              fileType: e.data.fileType,
              fileKey: e.data.fileKey
            }
          }
        })
      );
      if (!o_sendMessage.ok) {
        return resolve({
          ok: false,
          msg: `error when this.sendMessageMarkdown:${o_sendMessage.msg}`
        })
      }
      resolve({
        ok: true,
        msg: "ok",
        data: {
          message_id: o_sendMessage.data._id
        }
      })
    })
  }

  /**
   *
   * @returns {Promise<{ok:Boolean,errors:string[],message_id:string,files:Array<{path:string,datas:Array<{fileSize:Number,fileKey:String,fileName:String,fileType:String}> }> }>}
   * @param {String} teamId
   * @param {String} roomId
   * @param {string} search_keyword
   * @param {String} full_path
   * @param {number} [size_limit=500 * 1024 * 1024]
   * @memberof Jianliao
   */
  safelySendMessageToSaveDir(teamId, roomId, full_path, search_keyword, size_limit = 500 * 1024 * 1024, base_dir = full_path) {
    return new Promise(async resolve => {
      let o_stats = await Toolbox.getStats(full_path);
      let o_stats_ok = o_stats.ok && o_stats.stats.isDirectory()
      if (!o_stats_ok) {
        return resolve({
          ok: false,
          errors: [`${o_stats.ok ? `${full_path} is not a dir ` : o_stats.msg}`]
        })
      }
      let o_list = await Toolbox.safeListDir(full_path);

      /**@type {string[]} */
      let errors = [];
      /**@type {Array<{path:string,datas:Array<{fileSize:Number,fileKey:String,fileName:String,fileType:String}>  }>} */
      let files = [];
      for (let item of o_list) {
        if (item.stats.isFile()) {
          let o_strike = await this.getStrikeToken();
          if (!o_strike.ok) {
            errors.push(`upload ${path.relative(base_dir, item.full_path)} failed:unable to get strike token:${o_strike.msg}`);
            continue;
          }
          let o_up = await JianliaoWrapper.uploadFile(item.full_path,
            o_strike.token, size_limit,
            (full_path, parts_count, part_index) => {
              let f = path.relative(base_dir, full_path);
              while (f.includes("/")) {
                f = f.replace("/", "_#_")
              }
              while (f.includes("\\")) {
                f = f.replace("\\", "_#_")
              }
              f = path.join(base_dir, f);
              return JianliaoWrapper.DEFAULT_PART_NAME_FUNC(f, parts_count, part_index)
            });
          if (!o_up.ok) {
            errors.push(`upload ${path.relative(base_dir, item.full_path)} failed:${o_up.msg}`);
            continue;
          }
          files.push({
            path: path.relative(base_dir, item.full_path),
            datas: o_up.parts.map(e => {
              return {
                fileKey: e.data.fileKey,
                fileName: e.data.fileName,
                fileSize: e.data.fileSize,
                fileType: e.data.fileType
              }
            })
          });
        } else if (item.stats.isDirectory()) {
          let o_child = await this.safelySendMessageToSaveDir(teamId, roomId, item.full_path, search_keyword, size_limit, base_dir);
          errors = errors.concat(o_child.errors);
          files = files.concat(o_child.files);
        }
      }
      if (base_dir === full_path) {
        let o_sendMessage = await this.sendMesageRichtext(`<code><pre>${jsonBeautify({
          dir_full_path: full_path,
          errors: errors,
          files: files
        }, null, 1, 20)} </pre></code>\n\n\n`, search_keyword, teamId, roomId, files.map(e => e.datas)
          .reduce((a, b) => a.concat(b))
          .map(e => {
            return {
              category: "file",
              data: e
            }
          }));
        if (!o_sendMessage.ok) {
          return resolve({
            ok: false,
            errors: [`failed to send message : ${o_sendMessage.msg}`].concat(errors),
            files: files,
            message_id: ""
          })
        }
        resolve({
          ok: true,
          errors: errors,
          files: files,
          message_id: o_sendMessage.data._id
        })
      } else {
        resolve({
          ok: true,
          errors: errors,
          files: files,
          message_id: ""
        })
      }
    })
  }

  /**
   * @return {Promise<{ok:Boolean,msg:String,data:{message_id:String,errors:string[]}}>}
   * @param {String} teamId 
   * @param {String} roomId 
   * @param {String} dir_full_path must be a valid dir path
   */
  safelyStoreDirDaily1905(teamId, roomId, dir_full_path) {
    return new Promise(async resolve => {
      let fileList = await Toolbox.safeListAllFilesIn(dir_full_path);
      /**
       * @type {string[]}
       */
      let errors = [];
      /**@type {Array<{path:string,datas:Array<{fileSize:Number,fileKey:String,fileName:String,fileType:String}>  }>} */
      let files = [];
      for (let fp of fileList) {
        let o_strike = await this.getStrikeToken();
        let rela_path = path.relative(dir_full_path, fp)
        if (!o_strike.ok) {
          errors.push(`can not upload ${rela_path}:failed to get token ${o_strike.msg}`);
          continue;
        }
        let o_up = await JianliaoWrapper.uploadFile(fp,
          o_strike.token, undefined, (full_path, parts_count, part_index) => {
            let f = path.relative(dir_full_path, full_path);
            while (f.includes("_#_")) {
              f = f.replace("_#_", "_##_")
            }
            while (f.includes("/")) {
              f = f.replace("/", "_#_")
            }
            while (f.includes("\\")) {
              f = f.replace("\\", "_#_")
            }
            f = path.join(dir_full_path, f);
            return JianliaoWrapper.DEFAULT_PART_NAME_FUNC(f, parts_count, part_index)
          });
        if (!o_up.ok) {
          errors.push(`can not upload ${rela_path
            }:${o_up.msg}`)
        }
        if (!util.isArray(o_up.parts)) {
          return resolve({
            ok: false,
            msg: `o_up.parts is not Array:${util.inspect(o_up)}`
          })
        }
        files.push({
          path: rela_path,
          datas: o_up.parts.map(e => {
            return {
              fileKey: e.data.fileKey,
              fileName: e.data.fileName,
              fileSize: e.data.fileSize,
              fileType: e.data.fileType
            }
          })
        });
      }
      if (fileList.length > 0 && files.length == 0) {
        return resolve({
          ok: false,
          msg: `no file uploaded:${errors.join("\n---\n")}`
        })
      }
      let rtfContent = errors.length ?
        errors.map(e => `<code><pre>${e}</pre></code>`).join("<br>")
        : `<font color="#0099cc">all files uploaded successfully</font>`;
      rtfContent = `<h5>${dir_full_path}</h5><br>${rtfContent}`;
      let files_trans_func = () => {
        /**@type {Array<{category:"file",data:{fileKey:String,fileName:String,fileSize:Number,fileType:String}}>} */
        let results = [];
        for (let f of files) {
          for (let d of f.datas) {
            results.push({
              category: "file",
              data: {
                fileKey: d.fileKey,
                fileSize: d.fileSize,
                fileName: d.fileName,
                fileType: d.fileType
              }
            })
          }
        }
        return results;
        return files.map(e => e.datas.reduce((a, b) => {
          if (a && b && util.isArray(a)) {
            return a.concat(b);
          }
          console.log(`a.concat 问题:${dir_full_path}`);
          debugger
          return [];
        })).map(e => {
          return {
            category: "file",
            data: e
          }
        })
      }
      let o_sendMessage = await this.sendMesageRichtext(rtfContent, path.basename(dir_full_path),
        teamId, roomId, files_trans_func());
      if (!o_sendMessage.ok) {
        return resolve({
          ok: false,
          msg: `failed to send message:${o_sendMessage.msg}`
        })
      }
      resolve({
        ok: true,
        msg: "ok",
        data: {
          message_id: o_sendMessage.data._id,
          errors: errors
        }
      });
    })
  }

  /**
   * @return {Promise<{ok:Boolean,msg:String,data:{message_id:String,errors:string[]}}>}
   * @param {String} teamId 
   * @param {String} roomId 
   * @param {String} file_full_path must be a valid file path
   */
  safelyStoreFileDaily1905(teamId, roomId, file_full_path) {
    return new Promise(async resolve => {
      let fileList = [file_full_path]
      /**
       * @type {string[]}
       */
      let errors = [];
      /**@type {Array<{path:string,datas:Array<{fileSize:Number,fileKey:String,fileName:String,fileType:String}>  }>} */
      let files = [];

      for (let fp of fileList) {
        let o_strike = await this.getStrikeToken();
        let rela_path = path.basename(fp);
        if (!o_strike.ok) {
          errors.push(`can not upload ${rela_path}:failed to get token ${o_strike.msg}`);
          continue;
        }
        let o_up = await JianliaoWrapper.uploadFile(fp,
          o_strike.token, undefined, (full_path, parts_count, part_index) => {
            let f = path.basename(file_full_path)
            while (f.includes("_#_")) {
              f = f.replace("_#_", "_##_")
            }
            while (f.includes("/")) {
              f = f.replace("/", "_#_")
            }
            while (f.includes("\\")) {
              f = f.replace("\\", "_#_")
            }
            f = path.join(path.dirname(file_full_path), f);
            return JianliaoWrapper.DEFAULT_PART_NAME_FUNC(f, parts_count, part_index)
          });
        if (!o_up.ok) {
          errors.push(`can not upload ${rela_path
            }:${o_up.msg}`)
        }
        files.push({
          path: rela_path,
          datas: o_up.parts.map(e => {
            return {
              fileKey: e.data.fileKey,
              fileName: e.data.fileName,
              fileSize: e.data.fileSize,
              fileType: e.data.fileType
            }
          })
        });
      }
      if (fileList.length > 0 && files.length == 0) {
        return resolve({
          ok: false,
          msg: `no file uploaded:${errors.join("\n---\n")}`
        })
      }

      let rtfContent = errors.length ?
        errors.map(e => `<code><pre>${e}</pre></code>`).join("<br>")
        : `<font color="#0099cc">all files uploaded successfully</font>`;
      rtfContent = `<h5>${file_full_path}</h5>
      <br>${rtfContent}`;
      let files_trans_func = () => {
        /**@type {Array<{category:"file",data:{fileKey:String,fileName:String,fileSize:Number,fileType:String}}>} */
        let results = [];
        for (let f of files) {
          for (let d of f.datas) {
            results.push({
              category: "file",
              data: {
                fileKey: d.fileKey,
                fileSize: d.fileSize,
                fileName: d.fileName,
                fileType: d.fileType
              }
            })
          }
        }
        return results;
      };
      let o_sendMessage = await this.sendMesageRichtext(rtfContent, path.basename(file_full_path),
        teamId, roomId, files_trans_func());
      if (!o_sendMessage.ok) {
        return resolve({
          ok: false,
          msg: `failed to send message:${o_sendMessage.msg}`
        })
      }
      resolve({
        ok: true,
        msg: "ok",
        data: {
          message_id: o_sendMessage.data._id,
          errors: errors
        }
      });

    })
  }

  /**
   * @return {Promise<{ok:Boolean,msg:String,data:{message_id:String,errors:string[]}}>}
   * @param {String} teamId 
   * @param {String} roomId 
   * @param {String} target_full_path must be a valid file path
   */
  safelyStoreDaily1905(teamId, roomId, target_full_path) {
    return new Promise(async resolve => {
      let o_stats = await Toolbox.getStats(target_full_path);
      if (!o_stats.ok) {
        return resolve({
          ok: false,
          msg: `get stats error:${o_stats.msg}`
        })
      }
      if (o_stats.stats.isFile()) {
        resolve(await this.safelyStoreFileDaily1905(teamId, roomId, target_full_path))
      } else if (o_stats.stats.isDirectory()) {
        resolve(await this.safelyStoreDirDaily1905(teamId, roomId, target_full_path))
      }
      return resolve({
        ok: false,
        msg: `${target_full_path} is not a dir or file`
      })
    })
  }

  /**
   *
   *
   * @returns {Promise<{ok:boolean,msg:string,token:String}>}
   * @memberof Jianliao
   */
  getStrikeToken() {
    return new Promise(resolve => {
      axios.get(`https://jianliao.com/v2/strikertoken`, {
        headers: {
          cookie: this.cookies_as_request_header
        }
      }).then(axresp => {
        if (axresp.data && axresp.data.token) {
          resolve({
            ok: true,
            msg: "ok",
            token: axresp.data.token
          })
        } else {
          throw new Error(`axresp.data is toxic:${util.inspect(axresp.data)}`)
        }
      }).catch(axerr => {
        CommonAxerrHandlerGen(resolve)(axerr);
      })
    })
  }

  /**
   * @returns {Promise<{ok:Boolean,msg:String,valid:Boolean}>}
   */
  verify() {
    return new Promise(async resolve => {
      axios.get(`https://jianliao.com/v2/users/me`, {
        validateStatus: s => true,
        headers: {
          cookie: this.cookies_as_request_header
        }
      }).then(axresp => {
        if (axresp.status == 200) {
          resolve({
            ok: true,
            msg: `HTTP ${axresp.status} ${axresp.statusText}:${util.inspect(axresp.data)}`,
            valid: true
          })
        } else {
          resolve({
            ok: true,
            valid: false,
            msg: `HTTP ${axresp.status} ${axresp.statusText}:${util.inspect(axresp.data)}`
          })
        }
      }).catch(axerr => {
        CommonAxerrHandlerGen(resolve)(axerr);
      })
    })
  }

  /**
   * 
   * @return {Promise<{ok:Boolean,msg:String,data:{attachments:Array<ModelFileAttachment|ModelRtfAttachment>,body:String,createdAt:String }}>}
   * @param {String} message_id 
   * @param {String} teamId 
   * @param {String} roomId 
   */
  getMessageFilesByBesideId(message_id, teamId, roomId) {
    return new Promise(resolve => {
      axios.get(`https://jianliao.com/v2/messages`, {
        params: {
          _teamId: teamId,
          _roomId: roomId,
          _besideId: message_id,
          limit: 1
        },
        headers: {
          cookie: this.cookies_as_request_header,
          'referer': `https://jianliao.com/team/${teamId}/collection`
        },
        validateStatus: s => s == 200
      }).then(axresp => {
        if (util.isArray(axresp.data)
          && axresp.data.length) {
          let target = axresp.data.find(e => e._id === message_id);
          // debugger
          if (!target) {
            return resolve({
              ok: false,
              msg: `NOT FOUND.No message with _id ${message_id}`
            })
          }
          resolve({
            ok: true,
            msg: "ok",
            data: {
              body: target.body,
              attachments: target.attachments,
              createdAt: target.createdAt
            }
          })
        } else {
          throw new Error(`no sanity axresp:${util.inspect(axresp.data)}`)
        }
      }).catch(axerr => {
        CommonAxerrHandlerGen(resolve)(axerr);
      })
    })
  }





}

class ModelRtfAttachment {
  constructor() {
    this.category = "rtf";
    this.data = {
      id: '',
      text: '',
      title: ''
    }
    this._id = ""
  }
}

class ModelFileAttachment {
  constructor() {
    this.category = "file";
    this.data = {
      downloadUrl: '',
      fileKey: "",
      fileName: "",
      fileSize: 0,
      fileType: "js",
      id: "5cd4025bc07e384c3558364e",
      previewUrl: "",
      thumbnailUrl: ""
    }
    this._id = ""
  }
}



let JianliaoInstancePoll = {
  "null": new Jianliao
}

class JianliaoWrapper {

  /**
   * @description Cookie有效期默认为一个月
   * @returns {Promise<{ok:Boolean,msg:String,jianliao:Jianliao}>}
   * @param {String} mail
   * @param {String} pass
   * @memberof JianliaoWrapper
   */
  static loginByMailPass(mail, pass) {
    return new Promise(async resolve => {
      let j = new Jianliao;
      await j.__visit(`https://account.jianliao.com/signin`);
      axios.post(`https://account.jianliao.com/v1/email/signin`, {
        emailAddress: mail,
        password: pass
      }, {
          headers: {
            cookie: j.cookies_as_request_header,
            'x-language': 'zh',
            'x-requested-with': 'XMLHttpRequest',
            'content-type': 'application/json',
            'origin': 'https://account.jianliao.com',
            'referer': 'https://account.jianliao.com/signin',
          },
          validateStatus: s => s == 200
        }).then(axresp => {
          if (axresp.data && axresp.data._id && axresp.data.accountToken &&
            axresp.data.createdAt && axresp.data.emailAddress && axresp.data.updatedAt) {
            j.__dealWithSetCookie(axresp.headers["set-cookie"]);
            j.account_infomation.accountId = axresp.data._id;
            j.account_infomation.accountToken = axresp.data.accountToken;
            j.account_infomation.createAt = axresp.data.createdAt;
            j.account_infomation.emailAddress = axresp.data.emailAddress;
            j.account_infomation.updateAt = axresp.data.updatedAt
            resolve({
              ok: true,
              msg: "ok",
              jianliao: j
            })
          } else {
            throw new Error(`signin api response  不符合预期:${util.inspect(axresp.data)}`)
          }
        }).catch(axerr => {
          CommonAxerrHandlerGen(resolve)(axerr);
        })
    })
  }

  /**
   *
   * @return {Promise<{ok:Boolean,msg:String,jianliao:Jianliao}>}
   * @static
   * @param {String} mail
   * @param {String} pass
   * @memberof JianliaoWrapper
   */
  static getValidInstance(mail, pass) {
    return new Promise(async resolve => {
      let key = `${mail}:${pass}`;
      if (!!JianliaoInstancePoll[key]) {
        /**@type {Jianliao} */
        let ins = JianliaoInstancePoll[key];

        let o_verify = await ins.verify();
        if (o_verify.ok && o_verify.valid) {
          resolve({
            ok: true,
            msg: "ok",
            jianliao: ins
          })
        } else {
          JianliaoInstancePoll[key] = null;
          let o_retry = await this.getValidInstance(mail, pass);
          resolve(o_retry);
        }
      } else {
        let o_j = await this.loginByMailPass(mail, pass);
        if (o_j.ok) {
          JianliaoInstancePoll[key] = o_j.jianliao
          resolve({
            ok: true,
            msg: "ok",
            jianliao: o_j.jianliao
          });
        } else {
          CommonAxerrHandlerGen(resolve)("this.loginByMailPass" + o_j.msg);
        }
      }
    })
  }


  /**
   *
   *
   * @static
   * @param {import("fs").ReadStream} stream
   * @param {Number} file_size
   * @param {String} file_name
   * @param {String} strikeAuth 形如`Bearer eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJleHAiOjE1NDI2M...`
   * @returns {Promise<{ok:Boolean,msg:String,data:{fileKey:String}}>}
   * @memberof JianliaoWrapper
   */
  static uploadStream(stream, file_size, file_name, strikeAuth) {
    return new Promise(resolve => {
      let upload_url = `https://striker.teambition.net/upload?fileapi${Date.now()}`;
      let parsed = url.parse(upload_url);
      let form = new FormData;
      form.append("size", file_size);
      form.append("file", stream, {
        filename: file_name
      });
      /**@description 没有什么卵用的参数 */
      form.append("_file", file_name);
      let response_flag = false;
      form.on("end", () => {
        setTimeout(() => {
          if (!response_flag) {
            resolve({
              ok: false,
              msg: `[LATE RESPONSE] no http resp after form.end 1 minute`
            })
          }
        }, 60 * 1000)
      })
      form.submit({
        host: parsed.host,
        path: parsed.path,
        protocol: parsed.protocol,
        headers: {
          'origin': 'https://jianliao.com',
          'referer': 'https://jianliao.com/',
          "Authorization": strikeAuth,
          'user-agent': 'Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/64.0.3282.186 Safari/537.36 OPR/51.0.2830.55',
          'x-requested-with': 'XMLHttpRequest'
        }
      }, (err, resp) => {
        if (err) {
          return resolve({
            ok: false,
            msg: err.message
          })
        }
        response_flag = true;

        let buf = new buffer.Buffer("");
        resp.on("data", d => buf += d);
        resp.on("end", () => {
          if (resp.statusCode != 200) {
            return resolve({
              ok: false,
              msg: `resp.statusCode!=200 ==> ${resp.statusCode} ${resp.statusMessage}`
            })
          }
          try {
            let j = JSON.parse(buf);
            if (j.downloadUrl && j.fileKey) {
              resolve({
                ok: true,
                msg: "ok",
                data: j
              })
            } else {
              throw new Error(`response is toxic:${util.inspect(buf)}`)
            }
          } catch (e) {
            CommonAxerrHandlerGen(resolve)(e);
          }
        })
      })
    })
  }

  /**
   *
   *
   * @static
   * @param {String} full_path
   * @param {String} strikeAuth
   * @param {number} [size_limit=500 * 1024 * 1024]
   * @param {(full_path:string,parts_count:number,part_index:number)=>string} [part_name_func=this.DEFAULT_PART_NAME_FUNC]
   * @returns {Promise<{ok:Boolean,msg:String,parts:Array<{name:String,data:{fileSize:Number,fileKey:String,fileName:String,fileType:String}}>  }>}
   * @memberof JianliaoWrapper
   */
  static uploadFile(full_path, strikeAuth, size_limit = 999 * 1024 * 1024, part_name_func = this.DEFAULT_PART_NAME_FUNC) {
    return new Promise(async resolve => {
      let o_stats = await Toolbox.getStats(full_path);
      if (!o_stats.ok) {
        return resolve({
          ok: false,
          msg: `error when get stats :${o_stats.msg}`
        })
      }
      if (!o_stats.stats.isFile()) {
        return resolve({
          ok: false,
          msg: ` ${full_path} : it is not a file`
        })
      }
      let chunks_count = Math.ceil(o_stats.stats.size / size_limit);
      /**@type {Array<{name:String,data:{}}>} */
      let parts = [];
      for (let i = 0; i < chunks_count; i++) {
        let start = i * size_limit;
        let end = start + size_limit - 1;
        if (end >= o_stats.stats.size) {
          end = o_stats.stats.size - 1;
        }
        let o_upChunk = await this.uploadStream(
          fs.createReadStream(full_path, {
            start: start,
            end: end
          }),
          end - start + 1,
          part_name_func(full_path, chunks_count, i + 1),
          strikeAuth
        );
        if (!o_upChunk.ok) {
          return resolve({
            ok: false,
            msg: `error when uploading part ${i + 1}:${o_upChunk.msg}`
          })
        } else {
          parts.push({
            name: part_name_func(full_path, chunks_count, i + 1),
            data: o_upChunk.data
          });
        }
      }
      resolve({
        ok: true,
        msg: "ok",
        parts: parts
      })


    })
  }

  /**
   * @return {String}
   * @param {String} full_path 
   * @param {Number} parts_count 
   * @param {Number} part_index 
   */
  static DEFAULT_PART_NAME_FUNC(full_path, parts_count, part_index) {
    parts_count = parseInt(parts_count);
    if (parts_count == 1) {
      return path.basename(full_path);
    } else {
      let max_length = String(parts_count).length;
      let str_index = String(part_index);
      while (str_index.length < max_length) {
        str_index = `0${str_index}`
      }
      return `${path.basename(full_path)}.sf-part${str_index}`
    }
  }

}




module.exports = {
  JianliaoWrapper: JianliaoWrapper
}